home *** CD-ROM | disk | FTP | other *** search
- /* CEClassEditor+FileHandling.m
- *
- * This object controls the data of a beaker (molecules, cameras, groups etc.)
- * It is the main document of BeakerBoy and controls everything from loading to
- * setting up the browser which does most of the other work.
- *
- * For interface-info see the header file. The comments in this file mostly
- * cover only the real implementation details.
- *
- * Written by: Thomas Engel
- * Created: 23.10.1993 (Copyleft)
- * Last modified: 12.11.1994
- */
-
- #import <CEClassEditor+FileHandling.h>
- #import <CEMethod.h>
- #import <Text_MiscExtensions.h>
- #import <MiscSources.subproj/MiscStringArray_List.h>
- #import <MiscSources.subproj/MiscEmacsText.h>
-
- #import <misckit/MiscString.h>
- #import <misckit/MiscStringArray.h>
- #import <misckit/MiscSortedList.h>
-
- @implementation CEClassEditor ( FileHandling )
-
- - _try:path withAlternatives:anArray forText:aText andGetStyle:(int *)aStyle andSetPath:realPath
- {
- id ourPath;
- id theFilename;
- id aSearchPath;
- int trys;
-
- trys = 0;
- *aStyle = CE_FILETYPE_NONE;
- ourPath = [path copy];
- theFilename = [ourPath fileName];
-
- while( *aStyle == CE_FILETYPE_NONE &&
- trys <= [anArray count] )
- {
- [self _readFile:ourPath forText:aText
- andGetStyle:(int *)aStyle andSetPath:realPath];
-
- aSearchPath = [anArray objectAt:trys];
- [ourPath free];
- ourPath = nil;
- if( aSearchPath != nil )
- {
- // If it is a dot file then we should try the original path
- // again...actualle we shoul just skip it...I know..but this
- // works too.
-
- if( [aSearchPath cmp:"."] == 0 )
- ourPath = [path pathName];
- else if( [aSearchPath spotOfStr:"./"] == 0 )
- {
- ourPath = [path pathName];
- [ourPath addChar:[MiscString pathSeparator]];
- [ourPath concatenate:aSearchPath];
-
- // NOTE: We have to remove the local Dot reference
- // because somehow it does not work !!
-
- [ourPath replaceEveryOccurrenceOf:"/./" with:"/"];
- }
- else ourPath = [aSearchPath copy];
-
- [ourPath addChar:[MiscString pathSeparator]];
- [ourPath concatenate:theFilename];
- }
- trys++;
- }
-
- if( ourPath != nil ) [ourPath free];
- [theFilename free];
- return self;
- }
-
- - _readFile:path forText:aText andGetStyle:(int *)aStyle andSetPath:realPath
- {
- // We will try to load the requested file...and try to set the right
- // text type info.
-
- NXStream * aStream;
-
- // By default we won't allow anybody to edit a none existing text.
- // It won't get stored anyway because we do not have a path for
- // it.
-
- [aText setEditable:NO];
-
- [path replaceTildeWithHome];
- if( [path doesExistInFileSystem] == NO )
- return self;
-
- // Looks like we have a file...so lets get it into our text object.
- // We have to ensure that all the setting are in the proper fashion.
- // Setting the tabs for ASCII Files in one of them
-
- if( [self _isFileRTF:path] )
- {
- *aStyle = CE_FILETYPE_RTF;
- [aText setMonoFont:NO];
- }
- else
- {
- *aStyle = CE_FILETYPE_ASCII;
- [aText setMonoFont:YES];
- }
-
- [aText setEditable:YES];
-
- aStream = NXMapFile( [path stringValue], NX_READONLY );
- if( *aStyle == CE_FILETYPE_ASCII )
- [aText readText:aStream];
- else [aText readRichText:aStream];
- NXCloseMemory( aStream, NX_FREEBUFFER );
-
- // Now lets remember where we really did load it from.
-
- [realPath takeStringValueFrom:path];
-
- return self;
- }
-
- - (BOOL)_isFileRTF:path
- {
- // Tells us if the requested file is a RTF text.
- // Lets get the first characters and test for "{\rtf0"
- // If it is there...we should load the same text as RTF again.
- // Well quite stupid code..
-
- FILE * aStream;
- char magic[10];
- id aString;
- BOOL answer;
-
- magic[6] = 0;
- answer = NO;
-
- if( [path doesExistInFileSystem] )
- {
- aStream = fopen( [path stringValue], "r" );
- fgets( magic, 7, aStream );
- fclose( aStream );
- aString = [MiscString newWithString:magic];
-
- if( [aString cmp:"{\\rtf0"] == 0 )
- answer = YES;
-
- [aString free];
- }
- return answer;
- }
-
- - (const char *)filename
- {
- return [filename stringValue];
- }
-
- /*
- * Here come the methods we will implement to satisfy our window.
- * They are useful to change the inspector and handle oher tasks.
- */
-
- - _parseMethodFile
- {
- id newLine;
- id newMethod;
- int lines;
- int i;
-
- lines = [headerFile lineFromPosition:[headerFile textLength]];
-
- for( i=1; i<lines; i++ )
- {
- newLine = [headerFile substringFromLine:i];
- if ( [newLine charAt:0] == '-' ||
- [newLine charAt:0] == '+' )
- {
- newMethod = [[CEMethod alloc] initFromText:newLine];
- [methodList addObject:newMethod];
- }
- [newLine free];
- }
-
- return self;
- }
-
- - _writeText:aText withStyle:(int)aStyle to:path
- {
- // If the file does no exist...we will create it.
- // But it there is no real text for that file...we won't save anything !
- // RTFD requires a differnt style of saving !
-
- FILE * dummyStream;
- NXStream * aStream;
-
- if( aStyle == CE_FILETYPE_NONE ) return self;
-
- else if( aStyle == CE_FILETYPE_RTFD )
- return self;
-
- else
- {
- // Well the streams won't truncate a file so we have to do it on
- // our own.
- // We should do a backup of existing files here if requested.
-
- dummyStream = fopen( [path stringValue], "w" );
- if( dummyStream ) fclose( dummyStream );
-
- aStream = NXMapFile( [path stringValue], NX_WRITEONLY );
- switch( aStyle )
- {
- case CE_FILETYPE_RTF:
- [aText writeRichText:aStream];
- break;
- default:
- [aText writeText:aStream];
- }
- NXSaveToFile( aStream, [path stringValue] );
- NXCloseMemory( aStream, NX_FREEBUFFER );
- }
- return self;
- }
-
- - (BOOL)_selectSourceForMethod:aMethod
- {
- // This private method searches for the method inside the SourceFile
- // and _selects_ the code-portion of this method.
- // This really is only for finding the part.
- // Returns success and sets the internal range information.
-
- // Now the text[] part is more then just ugly..sorry.
-
- char text[10000];
- NXSelPt from;
- NXSelPt to;
- int i;
- int first;
- int last;
- BOOL gotIt;
-
- // Find the definition of the method.
-
- _sourceOriginStart = 0;
- _sourceOriginEnd = 0;
- [sourceFile setSel:0 :0];
- gotIt = [sourceFile findText:[aMethod name]
- ignoreCase:NO backwards:NO wrap:NO];
-
- if( ! gotIt ) return NO;
-
- // Now we assume that people stick to the rules and have the
- // '{' and '}' as the first characters of each line.
-
- [sourceFile getSel:&from :&to];
- [sourceFile getSubstring:text start:from.cp length:9990];
-
- // The first '{' is our start. A '0' terminates the string and the
- // search !
-
- gotIt = NO;
- first = -1;
- for( i=0; i<9990; i++ )
- {
- if( text[i] == 0 )
- break;
- else if( text[i] == '{' )
- {
- gotIt = YES;
- first = i;
- break;
- }
- }
-
- // Now we need a new-line followed by a '}'. A '0' terminates the string
- // and the search ! Same as before.
-
- gotIt = NO;
- last = -1;
- for( i=0; i<9990; i++ )
- {
- if( text[i] == 0 )
- break;
- else if( text[i] == '\n' &&
- text[i+1] == '}' )
- {
- gotIt = YES;
- last = i+2;
- break;
- }
- }
-
- // Now select the right area.
-
- if( gotIt )
- {
- _sourceOriginStart = from.cp+first;
- _sourceOriginEnd = from.cp+last;
- [sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
- }
-
- return gotIt;
- }
-
- - checkDocumentation:sender
- {
- // Now this method tries to get the documentation up to date.
- // Missing parts will be inserted...etc.pp.
-
- id aString;
- id aMethod;
- int i;
- id aList;
-
- // Be care full. we have the remember all the Docu and Source settings
- // becaus ehte method s we use will change them !!!
-
-
- // If there is no docu we ask the app to provide the default template.
-
- if( docuStyle == CE_FILETYPE_NONE )
- {
- [[NXApp delegate] copyClassDocuTemplate];
- [docuFile setMonoFont:NO];
- [docuFile setEditable:YES];
- [docuFile selectAll:self];
- [docuFile pasteFrom:_tempPb];
-
- // Be sure that we now have the right text type and a valid
- // path to save the docu to.
-
- docuStyle = CE_FILETYPE_RTF;
- _docuPath = [filename copy];
- [_docuPath cat:".rtf"];
-
- // The main template does need some customization to be useful
- // First the version
-
- [docuFile setSel:0 :0];
-
- if( [docuFile findText:"MyVersion"
- ignoreCase:NO backwards:NO wrap:NO] )
- [docuFile replaceSel:"0.1"];
-
- // Then the Date
-
- if( [docuFile findText:"1994"
- ignoreCase:NO backwards:NO wrap:NO] )
- [docuFile replaceSel:"1995"];
-
- // Now the Copyright part
-
- if( [docuFile findText:"MyCompany"
- ignoreCase:NO backwards:NO wrap:NO] )
- [docuFile replaceSel:"ClassEditor"];
-
- // Its time for the class name
-
- if( [docuFile findText:"MyClass"
- ignoreCase:NO backwards:NO wrap:NO] )
- {
- aString = [filename fileName];
- [docuFile replaceSel:[aString stringValue]];
- [aString free];
- }
-
- // Some header infos please
-
- if( [docuFile findText:"MyClass.h"
- ignoreCase:NO backwards:NO wrap:NO] )
- {
- aString = [filename fileName];
- [aString cat:".h"];
- [docuFile replaceSel:[aString stringValue]];
- [aString free];
- }
- }
-
- // Now we should have a documentation...but lets see if there is one
- // for every method. If not we should try to find the right place
- // to place a template.
-
- // We add the in a sorted order.
- // Because the _addDocu method does not take care of ordering...we
- // have to sort things the other way around.
-
- aList = [MiscSortedList new];
- [aList setSortOrder:Misc_DESCENDING];
- [aList setSortEnabled:YES];
-
- for( i=0; i<[methodList count];i++ )
- [aList addObject:[methodList objectAt:i]];
-
- for( i=0; i<[aList count];i++ )
- {
- aMethod = [aList objectAt:i];
- if( [self _selectDocuForMethod:aMethod] == 0 )
- [self _addDocuForMethod:aMethod];
- }
- [aList free];
-
- // That's it for today..
-
- return self;
- }
-
- - (BOOL)_selectDocuForMethod:aMethod
- {
- // This private method searches for the method inside the DocuFile
- // and _selects_ the docu-portion of this method.
- // This really is only for finding the part.
- // Returns the offset to the description text. An offset of '0' meens that
- // no method descript was found.
- // "See also:" is included..AND the blank lines below.
-
- // BUG: Now the text[] part is more then just ugly..sorry.
- // This limits us to methods with descriptions shorter then approx 100
- // lines. I hope that is enough.
- // I really should take the time and witch to a NXStream....
-
- char text[10000];
- NXSelPt from;
- NXSelPt to;
- NXSelPt tmpFrom;
- NXSelPt tmpTo;
- int first;
- int last;
- int i;
- BOOL gotIt;
- int blanks;
- int instancesStart = 0;
- id aString;
-
- // We have to find the right start-selection inside the documentation
- // first. This depends on the type of method.
-
- _docuOriginStart = 0;
- _docuOriginEnd = 0;
- [docuFile setSel:0 :0];
-
- gotIt = [docuFile findText:"Instance Methods"
- ignoreCase:NO backwards:NO wrap:NO
- font:[Font newFont:"Helvetica-Bold" size:18]];
- [docuFile getSel:&tmpFrom :&tmpTo];
- if( gotIt ) instancesStart = tmpFrom.cp;
-
- // If the method is a class method we should search for the right start
- // pos. The instanceStart is necessary anyway..because we need to check if
- // the found selector belongs to the right category.
- // Becasue this problem can only happen when we search for a class method
- // (they come first) we should be save that way.
-
- if( ![aMethod isInstanceMethod] )
- {
- [docuFile setSel:0 :0];
- gotIt = [docuFile findText:"Class Methods"
- ignoreCase:NO backwards:NO wrap:NO
- font:[Font newFont:"Helvetica-Bold" size:18]];
- }
- if( !gotIt ) return NO;
-
- // Well finding the right section is not enough.. we have to find the
- // right selector too. And sure...the font has to be correct. We
- // Don't want the find every ref to that method.
- // We also add a newline character to the end of the selector
- // because we are searching for the whole selector..and not
- // for a prefix inside another method.
-
- aString = [MiscString newWithString:[aMethod selectorName]];
- [aString cat:"\n"];
-
- gotIt = [docuFile findText:[aString stringValue]
- ignoreCase:NO
- backwards:NO
- wrap:NO
- font:[Font newFont:"Helvetica-Bold" size:14]];
- [aString free];
- if( !gotIt ) return NO;
-
- // Well now we should have the first selector for that method.
- // Now if the docu was created with CM it should work if we grab for
- // those right numbers of linefeeds.
- // Really nasty. Don't look at it.
-
- [docuFile getSel:&from :&to];
-
- // Now if the "class" method is inside the instance docu section
- // then we really don't have a method !
-
- if( ![aMethod isInstanceMethod] &&
- from.cp > instancesStart &&
- instancesStart > 0 )
- return NO;
-
- // Otherwise read the string...brrr...we really should have a stream here.
-
- [docuFile getSubstring:text start:from.cp length:9990];
-
- // Now we need the 2 last new-lines. A '0' terminates the string and the
- // search ! This is how we find the end of the docu.
-
- gotIt = NO;
- last = -1;
- for( i=0; i<9990; i++ )
- {
- if( text[i] == 0 )
- break;
- else if( text[i] == '\n' &&
- text[i+1] == '\n' &&
- text[i+2] == '\n' )
- {
- gotIt = YES;
- last = i;
- break;
- }
- }
- if( !gotIt ) return NO;
-
- // We always search for 2 blank lines (but they never come right after
- // each other..look at the styles!). If we don't find a newLine char
- // with the small Helvetica-Bold 3pt font then it seams like we have an
- // old docu style...this qives us one line less to search for.
- // A '0' terminates the string and the
- // search !
-
- blanks = 1;
-
- if( [docuFile findText:"\n"
- ignoreCase:NO
- backwards:NO
- wrap:NO
- font:[Font newFont:"Helvetica-Bold" size:3]] == YES )
- {
- [docuFile getSel:&tmpFrom :&tmpTo];
-
- // now if that small line really was in the scope of our currnet
- // method..then we have to pass 2 blank lines.
- // Btw..last is always valid because otherwise we won't be here.
-
- if( tmpFrom.cp < from.cp+last ) blanks = 0;
- }
-
- gotIt = NO;
- first = -1;
- for( i=0; i<9990; i++ )
- {
- if( text[i] == 0 )
- break;
- else if( text[i] == '\n' &&
- text[i+1] == '\n' )
- {
- blanks++;
- if( blanks == 2 )
- {
- gotIt = YES;
- first = i+2;
- break;
- }
- }
- }
-
- // Now select the right area.
-
- if( gotIt )
- {
- _docuOriginStart = from.cp+first;
- _docuOriginEnd = from.cp+last;
- [docuFile setSel:_docuOriginStart :_docuOriginEnd];
- }
-
- return gotIt;
- }
-
- - _addDocuForMethod:aMethod
- {
- // This method adds a template for the documentation of a certain method.
- // It DOES corrupt the pasteboard !
- // BUG: We also don't care about sorting ! This is bad !
-
- NXSelPt from;
- NXSelPt to;
- int i;
- int paramCount;
- BOOL gotIt;
- id tokens;
- id aString;
-
- // We have to find the right start-selection inside the documentation
- // first. This depends on the type of method.
-
- [docuFile setSel:0 :0];
-
- if( [aMethod isInstanceMethod] )
- gotIt = [docuFile findText:"Instance Methods"
- ignoreCase:NO backwards:NO wrap:NO
- font:[Font newFont:"Helvetica-Bold" size:18]];
- else gotIt = [docuFile findText:"Class Methods"
- ignoreCase:NO backwards:NO wrap:NO
- font:[Font newFont:"Helvetica-Bold" size:18]];
-
-
- if( !gotIt ) return self;
-
- // Now this is ugly...we should sort the methods alpahbetically !!
- // Buggy because we can only HOPE that nobody adds something behind the
- // "* Methods" section.
-
- [docuFile getSel:&from :&to];
- [docuFile setSel:to.cp+2 :to.cp+2];
-
- [[NXApp delegate] copyMethodDocuTemplate];
- [docuFile pasteFrom:_tempPb];
- [docuFile setSel:to.cp+2 :to.cp+2];
-
- [docuFile findText:"_CE_myMethod"
- ignoreCase:NO backwards:NO wrap:NO];
- [docuFile replaceSel:[aMethod selectorName]];
-
- [docuFile findText:"_CE_myMethod"
- ignoreCase:NO backwards:NO wrap:NO];
- [docuFile getSel:&from :&to];
- [docuFile replaceSel:[aMethod name]];
-
- // First lets fix the method prefix of the class methods and
- // remove the dulicate prefix"
-
- [docuFile setSel:from.cp-2 :from.cp-1];
-
- if( [aMethod isInstanceMethod] )
- {
- [docuFile findText:"- " ignoreCase:NO backwards:NO wrap:NO];
- [docuFile delete:self];
- }
- else
- {
- [docuFile replaceSel:"+"];
- [docuFile findText:"+ " ignoreCase:NO backwards:NO wrap:NO];
- [docuFile delete:self];
- }
-
- // Now its time to fix all the fonts.
- // Lets tokenize the methods name. This will create a list of
- // all the parts that have a different font.
- // We need to know how many parms the method takes to get a simpler
- // algorithm for coosing the fonts.
-
- tokens = [aMethod methodTokens];
- paramCount = [aMethod numberOfArguments];
-
- for( i=0; i<[tokens count]; i++ )
- {
- // Now select the new token...if we fail to find it..lets quit here.
-
- aString = [tokens objectAt:i];
- if( [docuFile findText:[aString stringValue]
- ignoreCase:NO backwards:NO wrap:NO] == NO ) break;
-
-
- // Depending on the type of token we will choose a different font.
- // First check for casts and then for paramerters.
-
- if( [aString grep:")"] )
- [docuFile setSelFont:[Font newFont:"Times-Roman" size:14]];
-
- // If it is not a cast the situation is a little bit trickier.
- // Imagine a - (BOOL)delegate or a simple -free method.
- // The token will be a part of the selector and still have no
- // ':' attached because there ist no argument
-
- else if( [aString grep:":"] == NO && paramCount > 0 )
- {
- [docuFile setSelFont:[Font newFont:"Times-Italic" size:14]];
- }
- }
-
- // The tokens are bound to die now.
-
- [[tokens freeObjects] free];
-
- return self;
- }
-
- @end
-
- /*
- * History: 13.01.95 Buh
- *
- *
- * Bugs: - ...
- */